package parking.naimi_trehel;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import parking.Door;
import parking.IAlgorithm;
import parking.LogManager;
import parking.Message;
import reso.Client;
import reso.Reso;

/**
 * Naimi Trehel implementation
 * This class implements Naimi Trehel algorithm based on the exchange of a token
 * @author Sophie and Ronnie
 * @date 9 November 2013
 */
public class Naimi_Trehel extends UnicastRemoteObject implements IAlgorithm, Client {
	private static final long serialVersionUID = -6371827064070264256L;
	private Door myDoor;				//instance of a door
	private int owner;					//ID of the site supposed to possess the token
	private int next;					//ID of the door to whom to send the token
	private boolean token;				//indicates whether a door possess the token
	private boolean criticalSection;	//indicates whether the site has made a request for the critical section
	private boolean electedNode;		//indicates whether a door is the elected node to possess the token at the beginning
	private Token tokenPark;			//token containing the number of spots in the parking
	private Reso myReso;				//distant object for communication between all the doors
	private LogManager myLogManager;	//object containing the logger
	private Logger myLogger;			//logger allowing to write in a file the execution trace of the algorithm
	
	/**
	 * Constructor used at initialization process of the algorithm
	 * @param door : Instance of a door executing NT algorithm
	 * @param nbSpots : Number of spots for the token initialization
	 * @param myReso : Distant object for the exchange of messages between doors
	 * @throws RemoteException : Exception thrown if ever distant objects are nor found
	 */
	public Naimi_Trehel(Door door, int nbSpots, Reso myReso) throws RemoteException {
		super();
		this.next = -1;
		this.token = false;
		this.criticalSection = false;

		this.myReso = myReso;		
		
		this.initialisation(door, nbSpots);
	}
	
	/**
	 * Initialization process for a door
	 * @param door : Instance of a door
	 * @param nbSpots : Number of spots within a parking
	 */
	public synchronized void initialisation(Door door, int nbSpots) {
		Set<Integer> allIdenties;
		this.myDoor = door;
		
		try {
			// Reso is responsible for attributing an identity to a door
			this.myDoor.setIdentity(myReso.declareClient(this));
		} catch (RemoteException e1) {
			e1.printStackTrace();
		}

		//path and degree for the log file (info for normal outputs, config for debug process)
		this.myLogManager = new LogManager("../logParking/Protocole_NT/" + "Log_NT" + this.myDoor.getIdentity() + ".log", Level.INFO);
		this.myLogger = this.myLogManager.getlogger();
		this.myLogger.config("Identité porte : " + this.myDoor.getIdentity());
		
		try {
			//recovery of all the other identities
			allIdenties = myReso.getClients();
			if (allIdenties.size() == 1) {
				//if i'm the first door to have declared myself to Reso
				//then i'm the elected door
				this.electedNode = true;
			} else { 
				this.electedNode = false;
			}
		} catch (RemoteException e) {
			e.printStackTrace();
		}
		
		//and if i'm the elected door, then i do possess the token
		if (this.electedNode) {
			this.token = true;
			this.owner = -1;
			this.tokenPark = new Token(nbSpots);
		} else {
			//it is the door having ID 0 who possesses the token
			this.owner = 0;
		}
		
	}
	
	/**
	 * Synchronized method used by a door when asking for the entrance or the exit of the parking
	 * @param entreeSortieVoiture represents whether a car is entering or leaving the parking
	 */
	public synchronized void demandeEntreeSC(int entreeSortieVoiture) {
		int nbPlacesTemp;
		
		if (entreeSortieVoiture == 0) {
			this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] : Entrée?");
		} else {
			this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] : Sortie?");
		}
		
		this.criticalSection = true;
		if (this.owner != -1) {
			try {
				//sending of a message to the owner of the token
				this.myReso.sendMessage(myDoor.getIdentity(), owner, Message.REQ);
				this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] -> Owner : " + this.owner + " | REQ");
			} catch (RemoteException e1) {
				e1.printStackTrace();
			}
			this.owner = -1;
			
			try {
				//waiting for the receipt of the token
				while (token == false) {
					this.myLogger.config("[PORTE" + this.myDoor.getIdentity() + "] : Attente jeton...");
					//wait will release the lock and will be waken up by a notify
					this.wait();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		//token has been received
		this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] : Entrée SC!");
		nbPlacesTemp = this.tokenPark.getSpots();
		
		switch(entreeSortieVoiture) {
		case 0 :
			//car entering the parking			
			if (this.tokenPark.getSpots() > 0) {
				this.tokenPark.decSpots();
				this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] : Voiture entrée | Avant : " + nbPlacesTemp + " | Après : " + this.tokenPark.getSpots());
			} else {
				this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] : Voiture refusée | Avant : " + nbPlacesTemp + " | Après : " + this.tokenPark.getSpots());
			}
			
			//i am in the critical section, i have let the car in if i had an available spot
			//i can now release the critical section and send the token to whom has asked for it
			demandeSortieSC();
			break;
		case 1 :
			//car leaving the parking
			this.tokenPark.incSpots();
			this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] : Voiture sortie | Avant : " + nbPlacesTemp + " | Après : " + this.tokenPark.getSpots());
			
			//i am in the critical section, i have let the car out
			//i can now release the critical section and send the token to whom has asked for it
			demandeSortieSC();
			break;
		}
		
	}
	
	/**
	 * Synchronized method for releasing the critical section
	 * The token is also sent to a door asking for it, if ever a request has been made
	 */
	public synchronized void demandeSortieSC() {
		this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] : Sortie SC!");
		this.criticalSection = false;
		
		//if i have a door asking for the token
		if ((this.next != -1) && (this.tokenPark != null)) {
			try {
				this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] -> " + next + " | Jeton");
				//then the token is sent to the caller
				this.myReso.sendMessage(myDoor.getIdentity(), next, tokenPark);
				this.token = false;
				this.next = -1;
				this.tokenPark = null;
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Accepts the request of a caller for the token
	 * If i do possess the token and i'm not asking for the critical section
	 * the token is immediately sent to the caller
	 * Otherwise, the caller will be the next door to whom I shall send the token after having been in the CS
	 * On the contrary, if i don't possess the token as i have just forwarded it to another door,
	 * then i shall transmit the request of the caller to the door i had forwarded it just before
	 * @param caller : ID of the door asking for the CS
	 */
	public synchronized void acceptRequest(int caller) {
		this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] <- " + caller + " | REQ ");
		if (this.owner == -1) {
			//i am the owner of the token and have asked for the CS
			if (this.criticalSection) {
				this.myLogger.info("[PORTE" + this.myDoor.getIdentity() + "] : Demande SC faite | " + "Prochaine porte : " + caller);
				this.next = caller;
			} else {
				//i haven't asked for the CS, sending the token to the caller 
				this.myLogger.info("[PORTE" + myDoor.getIdentity() + "] -> " + caller + " | Jeton");
				this.token = false;
				try {
					this.myReso.sendMessage(myDoor.getIdentity(), caller, tokenPark);
					this.tokenPark = null;
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		} else {
			//i am not the owner of the token as i have sent it before to another caller
			//sending the request of the caller to the door supposed to possess the token
			try {
				this.myLogger.info("[PORTE" + myDoor.getIdentity() + "] -> " + this.owner + " | REQ : " + caller);
				this.myReso.sendMessage(caller, this.owner, Message.REQ);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
		//update of the information about the caller
		//i know who is supposed to possess the token and if ever i have to make a request
		//i shall send this request to the supposed owner
		this.myLogger.info("[PORTE" + myDoor.getIdentity() + "] : Possession jeton : " + caller);
		this.owner = caller;
	}
	
	/**
	 * Reception of the token
	 * @param tokenPark : Token holding the number of spots available in the parking
	 */
	public synchronized void acceptToken(Token tokenPark) {
			this.token = true;
			this.tokenPark = tokenPark;
			this.myLogger.info("[PORTE" + myDoor.getIdentity() + "] : Réception jeton | Places dispo : " + tokenPark.getSpots());
			//i can now send a notification as i was waiting for it
			this.notify();
	}

	/**
	 * Overridden method of the IAlgorithm interface
	 * Used for the request of car wishing to enter the parking
	 */
	@Override
	public void demandeEntree() {
		this.demandeEntreeSC(0);
	}

	/**
	 * Overridden method of the IAlgorithm interface
	 * Used for the request of car wishing to exit the parking
	 */
	@Override
	public void demandeSortie() {
		this.demandeEntreeSC(1);
	}

	/**
	 * Overridden method of the Client interface
	 * Allows the reception of messages coming from other doors
	 */
	@Override
	public void receiveMessage(int from, int to, Serializable msg) throws RemoteException {
		if (msg instanceof Token) {
			//if message corresponds to a token
			this.acceptToken((Token) msg);
		} else if (msg == Message.REQ) {
			//message is a request
			this.acceptRequest(from);
		}		
	}
	
}
